#include <stdio.h>
#include <stdlib.h>

#include "../host/host.h"
#include "../log.h"
#include "../memmap.h"
#include "display.h"
#include "ge.h"
#include "gpu.h"

#include "texture.h"

#define MAX_TEX 255

typedef struct
{
    u32 addr;
    u32 hash;
    u32 w, h;
    u32 texture;
} TexCacheEntry;

TexCacheEntry TexCache[MAX_TEX] = {{0, 0, 0, 0, 0}}; /* TODO: STATIC */
u32 numtex = 0;

u32 tmpTexBuf32[1024 * 1024];
u16 tmpTexBuf16[1024 * 1024];

u32 clutBuf32[4096];
u16 clutBuf16[4096];

void updateSamplingParams()
{
    u8 minFilt = gstate.texfilter & 0x7;
    u8 magFilt = (gstate.texfilter >> 8) & 0x7;

    u8 sClamp = gstate.texwrap & 1;
    u8 tClamp = (gstate.texwrap >> 8) & 1;

    /* TODO: mipmaps ignored for now */
    minFilt &= 1;
    magFilt &= 1;

    host_setTexParam(sClamp, tClamp, magFilt, minFilt);
}

u32 getClutAddr(u32 clutEntrySize)
{
    return gstate.clutAddr + gstate.clutStart * clutEntrySize;
}

u32 getClutIndex(u32 index)
{
    return ((gstate.clutStart + index) >> gstate.clutShift) & gstate.clutMask;
}

u16 *readClut16()
{
    u32 clutNumEntries = gstate.clutBlocks * 16;
    u32 clutAddr = getClutAddr(2);
    u32 i;
    for (i = gstate.clutStart; i < clutNumEntries; i++)
        clutBuf16[i] = mem_read16(clutAddr + i * 2);
    return clutBuf16;
}

u32 *readClut32()
{
    u32 clutNumEntries = gstate.clutBlocks * 8;
    u32 clutAddr = getClutAddr(4);
    u32 i;
    for (i = gstate.clutStart; i < clutNumEntries; i++)
        clutBuf32[i] = mem_read32(clutAddr + i * 4);
    return clutBuf32;
}

void *unswizzleFromMem(u32 texAddr, u32 bytesPerPixel, u32 level)
{
    u32 addr = texAddr;
    u32 rowWidth = (bytesPerPixel > 0) ? (gstate.texBufWidth[level] * bytesPerPixel) : (gstate.texBufWidth[level] / 2);
    u32 pitch = rowWidth / 4;
    u32 bxc = rowWidth / 16;
    u32 byc = (gstate.texHeight[level] + 7) / 8;
    if (byc == 0)
        byc = 1;

    u32 ydest = 0;

    u32 by;
    for (by = 0; by < byc; by++)
    {
        if (rowWidth >= 16)
        {
            u32 xdest = ydest;
            u32 bx;
            for (bx = 0; bx < bxc; bx++)
            {
                u32 dest = xdest;
                u8 n;
                for (n = 0; n < 8; n++)
                {
                    u32 k;
                    for (k = 0; k < 4; k++) {
                        tmpTexBuf32[dest + k] = mem_read32(addr);
                        addr += 4;
                    }
                    dest += pitch;
                }
                xdest += 4;
            }
            ydest += (rowWidth * 8) / 4;
        }
        else if (rowWidth == 8)
        {
            u32 n;
            for (n = 0; n < 8; n++, ydest += 2)
            {
                tmpTexBuf32[ydest + 0] = mem_read32(addr + 0);
                tmpTexBuf32[ydest + 1] = mem_read32(addr + 4);
                addr += 16; /* skip 2 u32 */
            }
        }
        else if (rowWidth == 4)
        {
            u32 n;
            for (n = 0; n < 8; n++, ydest++) {
                tmpTexBuf32[ydest] = mem_read32(addr);
                addr += 16;
            }
        }
        else if (rowWidth == 2)
        {
            u32 n;
            for (n = 0; n < 4; n++, ydest++)
            {
                u16 n1 = mem_read32(addr +  0) & 0xffff;
                u16 n2 = mem_read32(addr + 16) & 0xffff;
                tmpTexBuf32[ydest] = (u32)n1 | ((u32)n2 << 16);
                addr += 32;
            }
        }
        else if (rowWidth == 1)
        {
            u32 n;
            for (n = 0; n < 2; n++, ydest++)
            {
                u8 n1 = mem_read32(addr +  0) & 0xf;
                u8 n2 = mem_read32(addr + 16) & 0xf;
                u8 n3 = mem_read32(addr + 32) & 0xf;
                u8 n4 = mem_read32(addr + 48) & 0xf;

                tmpTexBuf32[ydest] = (u32)n1 | ((u32)n2 << 8) | ((u32)n3 << 16) | ((u32)n4 << 24);
            }
        }
    }
    return tmpTexBuf32;
}

void *readIndexedTex(u32 level, u32 texAddr, u32 bytesPerIndex)
{
    u32 length = gstate.texBufWidth[level] * gstate.texHeight[level];
    void *buf = NULL;

    switch (gstate.clutMode)
    {
    case GE_CMODE_16BIT_BGR5650:
    case GE_CMODE_16BIT_ABGR5551:
    case GE_CMODE_16BIT_ABGR4444:
        {
        u16 *clut = readClut16();
        if (!gstate.texSwizzle)
        {
            u32 i;
            switch (bytesPerIndex)
            {
            case 1:
                for (i = 0; i < length; i++) {
                    u8 index = mem_read8(texAddr + i);
                    tmpTexBuf16[i] = clut[getClutIndex(index)];
                }
                break;

            case 2:
                for (i = 0; i < length; i++) {
                    u16 index = mem_read16(texAddr + i * 2);
                    tmpTexBuf16[i] = clut[getClutIndex(index)];
                }
                break;

            case 4:
                for (i = 0; i < length; i++) {
                    u32 index = mem_read32(texAddr + i * 4);
                    tmpTexBuf16[i] = clut[getClutIndex(index)];
                }
                break;
            }
        }
        else
        {
            u32 i, j;
            unswizzleFromMem(texAddr, bytesPerIndex, level);
            switch (bytesPerIndex)
            {
            case 1:
                for (i = 0, j = 0; i < length; i += 4, j++)
                {
                    u32 n = tmpTexBuf32[j];
                    u32 k;
                    for (k = 0; k < 4; k++) {
                        u8 index = (n >> (k * 4)) & 0xff;
                        tmpTexBuf16[i + k] = clut[getClutIndex(index)];
                    }
                }
                break;

            case 2:
                for (i = 0, j = 0; i < length; i += 2, j++)
                {
                    u32 n = tmpTexBuf32[j];
                    tmpTexBuf16[i + 0] = clut[getClutIndex(n & 0xffff)];
                    tmpTexBuf16[i + 1] = clut[getClutIndex(n >> 16)];
                }
                break;

            case 4:
                for (i = 0; i < length; i++) {
                    u32 n = tmpTexBuf32[i];
                    tmpTexBuf16[i] = clut[getClutIndex(n)];
                }
                break;
            }
        }
        buf = tmpTexBuf16;
        }
        break;

    case GE_CMODE_32BIT_ABGR8888:
        {
        u32 *clut = readClut32();
        if (!gstate.texSwizzle)
        {
            u32 i;
            switch (bytesPerIndex)
            {
            case 1:
                for (i = 0; i < length; i++) {
                    u8 index = mem_read8(texAddr + i);
                    tmpTexBuf32[i] = clut[getClutIndex(index)];
                }
                break;

            case 2:
                for (i = 0; i < length; i++) {
                    u16 index = mem_read16(texAddr + i * 2);
                    tmpTexBuf32[i] = clut[getClutIndex(index)];
                }
                break;

            case 4:
                for (i = 0; i < length; i++) {
                    u32 index = mem_read32(texAddr + i * 4);
                    tmpTexBuf32[i] = clut[getClutIndex(index)];
                }
                break;
            }
        }
        else
        {
            u32 j;
            s32 i;
            unswizzleFromMem(texAddr, bytesPerIndex, level);
            switch (bytesPerIndex)
            {
            case 1:
                for (i = length - 4, j = (length / 4) - 1; i >= 0; i -= 4, j--)
                {
                    u32 n = tmpTexBuf32[j];
                    u32 k;
                    for (k = 0; k < 4; k++) {
                        u32 index = (n >> (k * 8)) & 0xff;
                        tmpTexBuf32[i + k] = clut[getClutIndex(index)];
                    }
                }
                break;

            case 2:
                for (i = length - 2, j = (length / 2) - 1; i >= 0; i -= 2, j--)
                {
                    u32 n = tmpTexBuf32[j];
                    tmpTexBuf32[i + 0] = clut[getClutIndex(n & 0xffff)];
                    tmpTexBuf32[i + 1] = clut[getClutIndex(n >> 16)];
                }
                break;

            case 4:
                for (i = 0; (u32)i < length; i++) {
                    u32 n = tmpTexBuf32[i];
                    tmpTexBuf32[i] = clut[getClutIndex(n)];
                }
                break;
            }
        }
        buf = tmpTexBuf32;
        }
        break;

    default:
        _log(ERR, GFX, "Unhandled clut texture mode %d!!!", gstate.clutMode);
        break;
    }

    return buf;
}

RETexFormat getClutDestFormat(GEPaletteFormat format)
{
    switch (format)
    {
    case GE_CMODE_16BIT_ABGR4444:
        return RE_TEXFMT_16BIT_ABGR4444;
    case GE_CMODE_16BIT_ABGR5551:
        return RE_TEXFMT_16BIT_ABGR5551;
    case GE_CMODE_16BIT_BGR5650:
        return RE_TEXFMT_16BIT_BGR5650;
    case GE_CMODE_32BIT_ABGR8888:
        return RE_TEXFMT_32BIT_ABGR8888;
    }
    return 0;
}

u32 texByteAlignMap[] = {2, 2, 2, 4};

void setTexture()
{
    u32 i;

    TexCacheEntry entry;

    u32 bufw, w, h, texAddr;
    u32 texByteAlign = 4; /* default is 32-bit */
    GETextureFormat srcFmt;
    RETexFormat dstFmt = 0;
    u8 level = 0; /* TODO: support more textures */
    void *finalBuf = NULL;

    if (!mem_isOk(gstate.texAddr[level])) {
        _log(WRN, GFX, "Texture at %08x is not valid - not rendering", gstate.texAddr[level]);
        return;
    }

    _log(TRC, GFX, "Texture at %08x", gstate.texAddr[level]);

    for (i = 0; i < numtex; i++)
    {
        if (TexCache[i].addr == gstate.texAddr[level])
        {
            /* Validate the texture here (width, height etc) */
            TexCacheEntry entry = TexCache[i];

            if (entry.w == gstate.texWidth[level] && entry.h == gstate.texHeight[level]
                && entry.hash == (u32)mem_read32(gstate.texAddr[level]))
            {
                host_setTex(entry.texture);
                updateSamplingParams();
                _log(TRC, GFX, "Texture found in Cache, applying");
                return;
            }
            else
            {
                u32 j;
                host_delTex(TexCache[i].texture);
                numtex--;
                for (j = i; j < numtex; j++)
                    TexCache[j] = TexCache[j + 1];
                _log(TRC, GFX, "Texture overwritten, redecoding...");
            }
            break;
        }
    }

    _log(TRC, GFX, "No texture in TexCache, decoding...");

    /* we have to decode it */

    entry.addr = gstate.texAddr[level];
    entry.hash = mem_read32(gstate.texAddr[level]); /* TODO: add a "real" hash? */
    entry.texture = host_genTex();
    host_setTex(entry.texture);
            
    bufw = gstate.texBufWidth[level];
    
    w = gstate.texWidth[level];
    h = gstate.texHeight[level];
    texAddr = gstate.texAddr[level];

    entry.w = w;
    entry.h = h;

    srcFmt = (GETextureFormat)(gstate.texformat & 0xf);

    _log(INF, GFX, "Texture Width %04x Height %04x Bufw %04x Fmt %d Swizzle %d => ID: %d", w, h, bufw, srcFmt, gstate.texSwizzle, entry.texture);

    switch (srcFmt)
    {
    case GE_TFMT_CLUT4:
        dstFmt = getClutDestFormat(gstate.clutMode);
        switch (gstate.clutMode)
        {
        case GE_CMODE_16BIT_BGR5650:
        case GE_CMODE_16BIT_ABGR5551:
        case GE_CMODE_16BIT_ABGR4444:
            {
            u16 *clut = readClut16();
            u32 clutSharingOff = /*gstate.mipmapShareClut ? 0 : level * 16*/0;
            texByteAlign = 2;
            if (!gstate.texSwizzle)
            {
                u32 i;
                u32 addr = texAddr;
                for (i = 0; i < bufw * h; i += 2)
                {
                    u32 index = mem_read32(addr);
                    tmpTexBuf16[i + 0] = clut[getClutIndex((index >> 0) & 0xf) + clutSharingOff];
                    tmpTexBuf16[i + 1] = clut[getClutIndex((index >> 4) & 0xf) + clutSharingOff];
                    addr++;
                }
            }
            else
            {
                u32 i, j;
                unswizzleFromMem(texAddr, 0, level);
                for (i = 0, j = 0; i < bufw * h; i += 8, j++)
                {
                    u32 n = tmpTexBuf32[j];
                    u32 k, index;
                    for (k = 0; k < 8; k++) {
                        index = (n >> (k * 4)) & 0xf;
                        tmpTexBuf16[i + k] = clut[getClutIndex(index) + clutSharingOff];
                    }
                }
            }
            finalBuf = tmpTexBuf16;
            }
            break;

        case GE_CMODE_32BIT_ABGR8888:
            {
            u32 *clut = readClut32();
            u32 clutSharingOff = /*gstate.mipmapShareClut ? 0 : level * 16*/0;
            if (!gstate.texSwizzle)
            {
                u32 i;
                u32 addr = texAddr;
                for (i = 0; i < bufw * h; i += 2)
                {
                    u32 index = mem_read32(addr);
                    tmpTexBuf32[i + 0] = clut[getClutIndex((index >> 0) & 0xf) + clutSharingOff];
                    tmpTexBuf32[i + 1] = clut[getClutIndex((index >> 4) & 0xf) + clutSharingOff];
                    addr++;
                }
            }
            else
            {
                s32 i;
                u32 j;
                u32 pixels = bufw * h;
                unswizzleFromMem(texAddr, 0, level);
                for (i = pixels - 8, j = (pixels / 8) - 1; i >= 0; i -= 8, j--)
                {
                    u32 n = tmpTexBuf32[j];
                    u32 k, index;
                    for (k = 0; k < 8; k++) {
                        index = (n >> (k * 4)) & 0xf;
                        tmpTexBuf32[i + k] = clut[getClutIndex(index) + clutSharingOff];
                    }
                }
            }
            finalBuf = tmpTexBuf32;
            }
            break;

        default:
            _log(ERR, GFX, "Unknown CLUT4 texture mode %d", gstate.clutMode);
            break;
        }
        break;

    case GE_TFMT_CLUT8:
        finalBuf = readIndexedTex(level, texAddr, 1);
        dstFmt = getClutDestFormat(gstate.clutMode);
        texByteAlign = texByteAlignMap[gstate.clutMode];
        break;

    case GE_TFMT_CLUT16:
        finalBuf = readIndexedTex(level, texAddr, 2);
        dstFmt = getClutDestFormat(gstate.clutMode);
        texByteAlign = texByteAlignMap[gstate.clutMode];
        break;

    case GE_TFMT_CLUT32:
        finalBuf = readIndexedTex(level, texAddr, 4);
        dstFmt = getClutDestFormat(gstate.clutMode);
        texByteAlign = texByteAlignMap[gstate.clutMode];
        break;

    case GE_TFMT_4444:
    case GE_TFMT_5551:
    case GE_TFMT_5650:
        if (srcFmt == GE_TFMT_4444)
            dstFmt = RE_TEXFMT_16BIT_ABGR4444;
        else if (srcFmt == GE_TFMT_5551)
            dstFmt = RE_TEXFMT_16BIT_ABGR5551;
        else if (srcFmt == GE_TFMT_5650)
            dstFmt = RE_TEXFMT_16BIT_BGR5650;
        texByteAlign = 2;

        if (!gstate.texSwizzle)
        {
            u32 len = (bufw > w ? bufw : w) * h;
            u32 i;
            for (i = 0; i < len; i++)
                tmpTexBuf16[i] = mem_read16(texAddr + i * 2);
            finalBuf = tmpTexBuf16;
        }
        else
            finalBuf = unswizzleFromMem(texAddr, 2, level);
        break;

    case GE_TFMT_8888:
        dstFmt = RE_TEXFMT_32BIT_ABGR8888;
        if (!gstate.texSwizzle)
        {
            u32 len = bufw * h;
            u32 i;
            for (i = 0; i < len; i++)
                tmpTexBuf32[i] = mem_read32(texAddr + i * 4);
            finalBuf = tmpTexBuf32;
        }
        else
            finalBuf = unswizzleFromMem(texAddr, 4, level);
        break;

    case GE_TFMT_DXT1:
    case GE_TFMT_DXT3:
    case GE_TFMT_DXT5:
        _log(ERR, GFX, "Unhandled compressed texture!");
        break;

    default:
        _log(ERR, GFX, "Unknown Texture Format %d!!!", srcFmt);
        return;
    }

    host_setPixelStore(bufw, texByteAlign);
    host_setTexImage(level, w, h, dstFmt, finalBuf);
    host_setPixelStore(0, 1);

    updateSamplingParams();

    TexCache[numtex++] = entry;
}

u8 getBlendFix(float *color)
{
    if (color[0] == 0 && color[1] == 0 && color[2] == 0)
        return GE_FIX_BLACK;
    else if (color[0] == 1 && color[1] == 1 && color[2] == 1)
        return GE_FIX_WHITE;
    return GE_FIX_BLEND_COLOR;
}

void setBlendFunc()
{
    float *color = NULL;
    u8 srcfunc = gstate.srcfunc;
    u8 dstfunc = gstate.dstfunc;
    if (srcfunc == GE_FIX)
        srcfunc = getBlendFix(gstate.sfix);

    if (dstfunc == GE_FIX)
    {
        if (srcfunc == GE_FIX_BLEND_COLOR
         && gstate.sfix[0] + gstate.dfix[0] == 1
         && gstate.sfix[1] + gstate.dfix[1] == 1
         && gstate.sfix[2] + gstate.dfix[2] == 1) 
            dstfunc = GE_FIX_BLEND_ONE_MINUS_COLOR;
        else
            dstfunc = getBlendFix(gstate.dfix);
    }

    if (gstate.srcfunc == GE_FIX)
        color = gstate.sfix;
    else if (gstate.dstfunc == GE_FIX)
        color = gstate.dfix;

    if (color)
        host_setBlendColor(color);
    host_setBlendFactors(gstate.srcfunc, gstate.dstfunc);
}

